iT邦幫忙

0

[Flutter ][譯] FLUTTER + DJANGO APP (2.FLUTTER)

  • 分享至 

  • xImage
  •  

此文來自這位 youtuber BK Codes 的內容;但其實也不算翻譯,因為他的印度口音我還真的完全對不上頻率!! 因此主要為以我為小白角度將 code 上註解,若有誤解之處歡迎各位讀著更正賜教!
第二篇為 FLUTTER 部分,記得開啟 Django 後台(第一篇 Django 部分)

  1. FLUTTER 的 lib 資料結構如下:
lib
    |_ api
        |_api.dart # 連接後台,傳遞資料增減
    |_ models
        |_todo.dart # 序列化輸入資料 model
    |_ screens
        |_addTodo.dart # 開啟 screen 輸入增加資料
    |_ main.dart # 主程式
  1. pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  http: ^0.12.2  # 網絡請求
  provider: ^4.3.1 # 組件狀態共享
  cupertino_icons: ^0.1.3 # icon
  1. api.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import '../models/todo.dart';
import 'package:http/http.dart' as http;

class TodoProvider with ChangeNotifier {
  TodoProvider() {
    this.fetchTasks();
  }

  List<Todo> _todos = [];

  List<Todo> get todos { //使用get作為字首,運行屬性訪問器函式 getter
    // 向外暴露類中某個狀態適合使用setter,getter函式
    // 如果是觸發類中的某個行為操作普通函式較適合。
    return [..._todos]; //展開運算子 (Spread Operator),將兩個 List 結合在一起: [...list]
  } // main.dart 訪問並回傳資料

  void addTodo(Todo todo) async {
    final response = await http.post('http://10.0.2.2:8000/apis/v1/',
        headers: {"Content-Type": "application/json"}, body: json.encode(todo));
    if (response.statusCode == 201) { 
    //201 Created 成功狀態碼表示請求成功且有一個新的資源已經依據需要而被建立。
      todo.id = json.decode(response.body)['id']; //當資料產生時 django 會賦予一個 id 和 pk
      _todos.add(todo);  //加入新輸入的 model
      notifyListeners(); //重新渲染
    }
  }

  void deleteTodo(Todo todo) async {
    final response =
        await http.delete('http://10.0.2.2:8000/apis/v1/${todo.id}/');
    if (response.statusCode == 204) { 
    //204 No Content 成功狀態碼表明請求成功,但客戶端不需要更新目前的頁面。
      _todos.remove(todo);
      notifyListeners();
    }
  }

  fetchTasks() async {
    final url = 'http://10.0.2.2:8000/apis/v1/?format=json';
    final response = await http.get(url);
    if (response.statusCode == 200) {
      var data = json.decode(response.body) as List;
      //print('data::$data');
      // data 輸出為 [json, json, json...]
      _todos = data.map<Todo>((json) => Todo.fromJson(json)).toList();
      //print('_todos::$_todos');
      // _todos 輸出為 [Instance, Instance, Instance...]
    }
  }
}

// convert Map to List of Objects
// https://stackoverflow.com/questions/57234575/dart-convert-map-to-list-of-objects
  1. todo.dart
class Todo {
  int id;
  final String title;
  final String description;

  Todo({this.id, this.title, this.description});

  factory Todo.fromJson(Map<String, dynamic> json) {
    return Todo(
        id: json['id'], title: json['title'], description: json['description']);
  }
  dynamic toJson() => {'id': id, 'title': title, 'description': description};
}
// factory 工廠構造函數
// 工廠構造函數不能和使用this關鍵字來使用class的屬性和方法。只能使用類中static類型的屬性和方法。抽像類只能被繼承或者當做接口,不能被實例化。但是抽像類中的工廠構造函數是可以實例化(return)如果抽像類使用factory 類名._(){ return null;},這個抽像類將不能被繼承,只能當做接口
// https://juejin.cn/post/6844904017991221255

// 抽像類 Todo, 用以驗證輸入格式是否正確(int, String...)
// factory 用於將輸入內容物返回實例化
  1. addTodo.dart
import 'package:app/api/api.dart';
import 'package:app/models/todo.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class AddTodoScreen extends StatefulWidget {
  @override
  _AddTodoScreenState createState() => _AddTodoScreenState();
}

class _AddTodoScreenState extends State<AddTodoScreen> {
  final todoTitleController = TextEditingController();
  final todoDesController = TextEditingController();
  // TextEditingController : 響應文本框內容的更改,據輸入內容來更新結果

  void onAdd() {
    final String textVal = todoTitleController.text;
    final String desVal = todoDesController.text;

    if (textVal.isNotEmpty && desVal.isNotEmpty) {
      final Todo todo = Todo(title: textVal, description: desVal); // 放入 models Todo
      Provider.of<TodoProvider>(context, listen: false).addTodo(todo);
      // Provider.of with listen:false : 調用但不使 widget 被重構
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Add Todo')),
      body: ListView(
        children: [
          Container(
              child: Column(
            children: [
              TextField(
                controller: todoTitleController,
                // TextEditingController 綁定 TextField,開始監聽文本框的變化。
              ),
              TextField(
                controller: todoDesController,
              ),
              RaisedButton(
                  child: Text('Add'),
                  onPressed: () {
                    onAdd();
                    Navigator.of(context).pop();
                    // 執行加入資料後
                    // Navigator.pop, 刪除目前 screen, 回到 list
                  })
            ],
          ))
        ],
      ),
    );
  }
}

6.main.dart

import 'package:app/api/api.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './screens/addTodo.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider( //ChangeNotifierProvider 可向其所有子節點指定聽取一個 ChangeNotifier
      create: (context) => TodoProvider(), // api.dart 所建構的 ChangeNotifier 指定於此
      child: MaterialApp( // MaterialApp 控制主題色彩等等
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: HomePage(),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final todoP = Provider.of<TodoProvider>(context); // 監控 TodoProvider
    return Scaffold(
      appBar: AppBar(
        title: Text('Todo App'),
      ),
      body: ListView.builder(
        shrinkWrap: true,
        itemCount: todoP.todos.length,
        itemBuilder: (BuildContext context, int index) {
          //print('index::$index'); // itemBuilder 捲到才顯示該資料
          return ListTile(
              trailing: IconButton(
                  icon: Icon(Icons.delete, color: Colors.red),
                  onPressed: () {
                    todoP.deleteTodo(todoP.todos[index]);
                    // todoP.todos 為一個 list [Instance, Instance, Instance...]
                    // 固可由 ListView 的 index 指定該資料操作
                  }),
              title: Text(
                todoP.todos[index].title,
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
              subtitle: Text(
                todoP.todos[index].description,
                style: TextStyle(fontSize: 15, color: Colors.black),
              ));
        },
      ),
      floatingActionButton: FloatingActionButton(
          child: Icon(
            Icons.add,
            size: 30,
          ),
          onPressed: () {
            Navigator.of(context)
                .push(MaterialPageRoute(builder: (ctx) => AddTodoScreen()));
            // 當使用 Navigator 導向其他 screen 時有二種方式可使用
            // Navigator.push, 直接疊上新 screen
            // Navigator.pop, 刪除目前 screen/
            // MaterialPageRoute 指用 context 的規則不甚瞭解,也未找到想詳細資料,有待釐清
          }),
    );
  }
}

註: 資料出處
Part1: https://www.youtube.com/watch?v=hfee7SIwUTs
Part2: https://www.youtube.com/watch?v=fyndW3s9t6M
Part3: https://www.youtube.com/watch?v=LW-220zNA2E

GITHUB:https://github.com/bayardkalyan/flutter-django-fullstack


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言